cmake_minimum_required(VERSION 3.10)

if (NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

project(zeno
    LANGUAGES CXX C
    DESCRIPTION "An indispensable node-based 3D game engine."
    HOMEPAGE_URL https://github.com/zenustech/zeno3-poc
    VERSION 3.0.0
    )

if (PROJECT_BINARY_DIR STREQUAL PROJECT_SOURCE_DIR)
    message(FATAL_ERROR "The binary directory of CMake cannot be the same as source directory! "
        "Do not use 'cmake .'! Please consider use 'cmake -B build' to specify a different binary directory. "
        "Otherwise this project may fail to build, or make Git hard to exclude binary files. "
        "For Windows, it is highly recommended to use Visual Studio as IDE for CMake projects.")
endif()

if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
    set(CMAKE_INSTALL_PREFIX ${PROJECT_BINARY_DIR}/installed CACHE PATH "set install directory to build/installed" FORCE)
endif()

if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
    message(FATAL_ERROR "Zeno only support 64-bit builds, 32-bit is not allowed for now. "
        "If you use modern Visual Studio, please switch to x64-Release instead of x86-Release! "
        "If you use command line CMake, consider use cmake -A x64 instead of cmake -A win32.")
endif()

option(ZENO_USE_SYSTEM_TBB "Use system-wide TBB instead of bundled TBB" OFF)
option(ZENO_USE_SYSTEM_ZLIB "Use system-wide ZLIB instead of bundled ZLIB" OFF)
option(ZENO_DISABLE_MINILOG "Disable built-in log system for Zeno" OFF)
option(ZENO_ENABLE_BACKTRACE "Enable stack backtrace in Zeno" ON)
option(ZENO_ENABLE_TBBMALLOC "Enable tbbmalloc_proxy for Zeno" OFF)
option(ZENO_ENABLE_MIMALLOC "Enable mimalloc override for Zeno" ON)
option(ZENO_ENABLE_DOCVIEW "Enable building doc viewer for Zeno" ON)
option(ZENO_ENABLE_GPROF "Generate GNU profiling info for Zeno" OFF)
option(ZENO_ENABLE_FAST_MATH "Enable -ffast-math option for Zeno" ON)
option(ZENO_ENABLE_AVX2 "Enable -march=skylake option for Zeno" ON)
option(ZENO_ENABLE_SPLASH "Show cool splash screen on start of Zeno" ON)
option(ZENO_INSTALL_TARGETS "Build install targets for Zeno" ON)
option(ZENO_RUN_CPACK "Use CPack for packaging Zeno" ON)

set(CMAKE_INSTALL_RPATH \$ORIGIN/../lib)
set(CMAKE_BUILD_RPATH \$ORIGIN)
set(ZENO_OUTPUT_DIR ${PROJECT_BINARY_DIR}/output)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${ZENO_OUTPUT_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${ZENO_OUTPUT_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${ZENO_OUTPUT_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${ZENO_OUTPUT_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${ZENO_OUTPUT_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${ZENO_OUTPUT_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${ZENO_OUTPUT_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${ZENO_OUTPUT_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${ZENO_OUTPUT_DIR})

if (NOT MSVC)
    find_program(CCACHE_PROGRAM ccache)  # Compilation speed up: https://ccache.dev
    if (CCACHE_PROGRAM)
        message(STATUS "Found CCache: ${CCACHE_PROGRAM}")
        set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM})
        set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PROGRAM})
    endif()
endif()

if (WIN32)
    # rid min and max lowercase macros (from windows.h), get back M_PI, M_E math constant macros (in math.h)
    add_compile_definitions(-DNOMINMAX -D_USE_MATH_DEFINES)
endif()

if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
    set(CMAKE_CXX_FLAGS_DEBUG "-Og -ggdb")
    if (ZENO_ENABLE_FAST_MATH)
        set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -fno-omit-frame-pointer -gdwarf")
        set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
    else()
        set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-Ofast -fno-omit-frame-pointer -gdwarf")
        set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -DNDEBUG")
    endif()
    set(CMAKE_CXX_FLAGS_MINSIZEREL "-Oz -DNDEBUG")
    if (DEFINED ENV{PGO_STAGE} AND ENV{PGO_STAGE} MATCHES "generate|use")
        message(STATUS "PGO stage: $ENV{PGO_STAGE}")
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-$ENV{PGO_STAGE}")
        set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-$ENV{PGO_STAGE}")
        set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fprofile-$ENV{PGO_STAGE}")
    endif()
    if (ZENO_ENABLE_GPROF)
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
        set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
        set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg")
    endif()
endif()

file(GLOB_RECURSE project_sources CONFIGURE_DEPENDS src/*.cpp)
file(GLOB_RECURSE project_headers CONFIGURE_DEPENDS include/*.h include/*.hpp)
add_executable(${PROJECT_NAME} ${project_sources} ${project_headers})
target_include_directories(${PROJECT_NAME} PUBLIC
    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/3rd/include>
    # $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
    $<INSTALL_INTERFACE:include>
)
# target_precompile_headers(${PROJECT_NAME} PRIVATE
#     include/zenox/minilog.hpp
#     include/zenox/glm.hpp
#     include/zenoui/check_gl.hpp
#     include/zenoui/Event.hpp
#     include/zenoui/Rect.hpp
#     include/zenoui/Shape.hpp
#     include/zenoui/Widget.hpp
#     include/zenoui/Property.hpp
#     include/zenox/unicode.hpp
#     include/zenox/raiiutils.hpp
#     include/zenom/GraphModel.hpp
# )
# set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON)

if (ZENO_INSTALL_TARGETS)
    install(DIRECTORY include/ 3rd/include/ DESTINATION include COMPONENT "develop")
    install(TARGETS ${PROJECT_NAME}
        EXPORT ZenoTargets
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib COMPONENT "develop"
    )
endif()

target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_SOURCE_DIR=\"${PROJECT_SOURCE_DIR}/\")
target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_VERSION_MAJOR=${PROJECT_VERSION_MAJOR})
target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_VERSION_MINOR=${PROJECT_VERSION_MINOR})
target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_VERSION_PATCH=${PROJECT_VERSION_PATCH})
target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_BUILD_TYPE=\"${CMAKE_BUILD_TYPE}\")
target_compile_definitions(${PROJECT_NAME} PUBLIC $<$<CONFIG:Release>:-DZENO_BUILD_CONFIG=\"Release\"> $<$<CONFIG:Debug>:-DZENO_BUILD_CONFIG=\"Debug\">)
target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_COMPILER_ID=\"${CMAKE_CXX_COMPILER_ID}\")
target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_PLATFORM_ID=\"${CMAKE_CXX_PLATFORM_ID}\")

add_subdirectory(3rd/glm)
target_link_libraries(${PROJECT_NAME} PUBLIC glm)
add_subdirectory(3rd/glfw)
target_link_libraries(${PROJECT_NAME} PUBLIC glfw)
add_subdirectory(3rd/glad)
target_link_libraries(${PROJECT_NAME} PUBLIC glad)
add_subdirectory(3rd/jsoncpp)
target_link_libraries(${PROJECT_NAME} PUBLIC jsoncpp_lib)
add_subdirectory(3rd/cppcodec)
target_link_libraries(${PROJECT_NAME} PUBLIC cppcodec)
add_subdirectory(3rd/robin-map)
target_link_libraries(${PROJECT_NAME} PUBLIC robin_map)
add_subdirectory(3rd/abseil-cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC
    absl::base
    absl::algorithm
    absl::flat_hash_map
    absl::inlined_vector
    absl::meta
    absl::numeric
    absl::str_format
    absl::strings
    absl::synchronization
    absl::time
    absl::cord
    absl::crc32c
    absl::debugging
    absl::hash
)

include(CheckCXXSourceCompiles)
check_cxx_source_compiles([[
#include <format>
#include <string>
#include <iostream>

int main() {
    std::string s = std::format("Hello {}", 42);
    std::cout << s << std::endl;
    return 0;
}
]] CXX_COMPILES_STD_FORMAT)
if (CXX_COMPILES_STD_FORMAT)
    message(STATUS "Using C++20 std::format")
    target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_CPP20_FORMAT)
else()
    message(WARNING "The C++ compiler did not support std::format, fallback to fmt::format instead")
    add_subdirectory(3rd/fmt)
    target_link_libraries(${PROJECT_NAME} PUBLIC fmt)
endif()
set(CMAKE_REQUIRED_QUIET FALSE)
check_cxx_source_compiles([[
#include <experimental/simd>

int main() {
    std::experimental::native_simd<float> s;
    return 0;
}
]] CXX_COMPILES_STD_EXPERIMENTAL_SIMD)
if (CXX_COMPILES_STD_EXPERIMENTAL_SIMD)
    message(STATUS "Using C++23 std::experimental::simd")
    target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_CPP23_EXPERIMENTAL_SIMD)
else()
    message(STATUS "The C++ compiler did not support std::experimental::simd")
endif()

if (ZENO_USE_SYSTEM_ZLIB)
    find_package(ZLIB REQUIRED)
    target_link_libraries(${PROJECT_NAME} PUBLIC ${ZLIB_LIBRARIES})
    target_include_directories(${PROJECT_NAME} PUBLIC ${ZLIB_INCLUDE_DIR})
    target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_SYSTEM_ZLIB_VERSION=\"${ZLIB_VERSION}\")
else()
    add_subdirectory(3rd/zlib)
    target_link_libraries(${PROJECT_NAME} PUBLIC zlib)
endif()

if (ZENO_DISABLE_MINILOG)
    target_compile_options(${PROJECT_NAME} PUBLIC -DZENO_MINILOG_DISABLE)
endif()

if (ZENO_ENABLE_BACKTRACE)
    add_subdirectory(3rd/backward-cpp)
    add_backward(${PROJECT_NAME})
    target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_BACKWARD_CPP)
endif()

find_package(OpenMP)
if (OpenMP_FOUND AND TARGET OpenMP::OpenMP_CXX)
    target_link_libraries(${PROJECT_NAME} PUBLIC OpenMP::OpenMP_CXX)
    if (MSVC) # add /openmp:llvm to enable OpenMP 4.0 support
        target_compile_options(${PROJECT_NAME} PUBLIC $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:-openmp:llvm>)
    endif()
    target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_OPENMP)
endif()

find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads)
target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_DL_LIBS})

if (ZENO_USE_SYSTEM_TBB)
    find_package(TBB CONFIG REQUIRED)
    message(STATUS "Using system TBB found in ${TBB_DIR}")
    target_link_libraries(${PROJECT_NAME} PUBLIC TBB::tbb)
    if (ZENO_ENABLE_TBBMALLOC AND TARGET TBB::tbbmalloc AND TARGET TBB::tbbmalloc_proxy)
        if (ZENO_ENABLE_MIMALLOC)
            message(FATAL_ERROR "ZENO_ENABLE_MIMALLOC and ZENO_ENABLE_TBBMALLOC cannot be ON at the same time")
        endif()
        message(STATUS "Enabling tbbmalloc_proxy")
        target_link_libraries(${PROJECT_NAME} PUBLIC TBB::tbbmalloc TBB::tbbmalloc_proxy)
        target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_TBBMALLOC_PROXY)
    endif()
    target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_SYSTEM_TBB_VERSION=\"${TBB_VERSION}\")
else()
    message(STATUS "Using bundled TBB in 3rd/tbb")
    set(TBB_BUILD_SHARED ON CACHE BOOL "" FORCE)
    set(TBB_BUILD_STATIC OFF CACHE BOOL "" FORCE)
    set(TBB_BUILD_TBBMALLOC ${ZENO_ENABLE_TBBMALLOC} CACHE BOOL "" FORCE)
    set(TBB_BUILD_TBBMALLOC_PROXY ${ZENO_ENABLE_TBBMALLOC} CACHE BOOL "" FORCE)
    add_subdirectory(3rd/tbb)
    target_link_libraries(${PROJECT_NAME} PUBLIC tbb)
    if (ZENO_ENABLE_TBBMALLOC AND TARGET tbbmalloc AND TARGET tbbmalloc_proxy)
        if (ZENO_ENABLE_MIMALLOC)
            message(FATAL_ERROR "ZENO_ENABLE_MIMALLOC and ZENO_ENABLE_TBBMALLOC cannot be ON at the same time")
        endif()
        message(STATUS "Enabling tbbmalloc_proxy")
        target_link_libraries(${PROJECT_NAME} PUBLIC tbbmalloc tbbmalloc_proxy)
        target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_TBBMALLOC_PROXY)
    endif()
endif()

if (ZENO_ENABLE_MIMALLOC)
    set(MI_BUILD_TESTS OFF CACHE BOOL "" FORCE)
    set(MI_BUILD_SHARED ON CACHE BOOL "" FORCE)
    set(MI_BUILD_STATIC OFF CACHE BOOL "" FORCE)
    set(MI_BUILD_OBJECT OFF CACHE BOOL "" FORCE)
    set(MI_OVERRIDE ON CACHE BOOL "" FORCE)
    add_subdirectory(3rd/mimalloc)
    target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_MIMALLOC)
    target_link_libraries(${PROJECT_NAME} PRIVATE mimalloc)
    target_sources(${PROJECT_NAME} PRIVATE ci/mimalloc-new-delete.cpp)
endif()

include(CheckCXXCompilerFlag)
if (MSVC)
    # fix string literial cannot include unicode characters on windows
    check_cxx_compiler_flag(-utf-8 CXX_SUPPORTS_MSVC_UTF8)
    if (CXX_SUPPORTS_MSVC_UTF8)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:-utf-8>)
    else()
        message(WARNING "the MSVC compiler seems not supporting /utf-8 option, compilation may fail")
    endif()
    # fix __VA_OPT__ not working well conformant to C++20 standard
    check_cxx_compiler_flag(-Zc:preprocessor CXX_SUPPORTS_MSVC_ZC_PREPROCESSOR)
    if (CXX_SUPPORTS_MSVC_ZC_PREPROCESSOR)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:-utf-8>)
    else()
        message(WARNING "the MSVC compiler seems not supporting /Zc:preprocessor option, compilation may fail")
    endif()
    # fix MSVC compile failure when a single object file is larger than 65279 bytes
    check_cxx_compiler_flag(-bigobj CXX_SUPPORTS_MSVC_BIGOBJ)
    if (CXX_SUPPORTS_MSVC_BIGOBJ)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:-bigobj>)
    else()
        message(WARNING "the MSVC compiler seems not supporting /bigobj option, compilation may fail")
    endif()
    # disable MSVC's meanless noisy warnings, we'd use GCC on CI for discovering problems
    check_cxx_compiler_flag(-w CXX_SUPPORTS_MSVC_W)
    if (CXX_SUPPORTS_MSVC_W)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:-w>)
    endif()
    # enable MSVC's run-time checks to discovering undefinded behaviors in Debug build
    check_cxx_compiler_flag(-RTCsu CXX_SUPPORTS_MSVC_RTCSCU)
    if (CXX_SUPPORTS_MSVC_RTCSU)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:$<$<CONFIG:Debug>:-RTCsu>>)
    endif()
    # enable MSVC's Just-My-Code debugging informations to please debugger users
    check_cxx_compiler_flag(-JMC CXX_SUPPORTS_MSVC_JMC)
    if (CXX_SUPPORTS_MSVC_JMC)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:$<$<CONFIG:Debug>:-JMC>>)
    endif()
    # Enable even more aggressive function inlining for MSVC
    check_cxx_compiler_flag(-Ob3 CXX_SUPPORTS_MSVC_OB3)
    if (CXX_SUPPORTS_MSVC_OB3)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:$<$<CONFIG:Release>:-Ob3>>)
    endif()
    # https://devblogs.microsoft.com/cppblog/introducing-gw-compiler-switch/
    check_cxx_compiler_flag(-Gw CXX_SUPPORTS_MSVC_GW)
    if (CXX_SUPPORTS_MSVC_GW)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:$<$<CONFIG:Release>:-Gw>>)
    endif()
    # Forbid non-standard conforming feature that only work in MSVC
    check_cxx_compiler_flag(-permissive- CXX_SUPPORTS_MSVC_PERMISSIVE_MINUS)
    if (CXX_SUPPORTS_MSVC_PERMISSIVE_MINUS)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:$<$<CONFIG:Release>:-permissive->>)
    endif()
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
    if (CMAKE_BUILD_TYPE MATCHES "[Dd][Ee][Bb][Uu][Gg]")
        # additional UB checks in Debug build
        check_cxx_compiler_flag(-ftrapv CXX_SUPPORTS_FTRAPV)
        if (CXX_SUPPORTS_FTRAPV)
            target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-ftrapv>)
        endif()
        check_cxx_compiler_flag(-fbounds-check CXX_SUPPORTS_FBOUNDS_CHECK)
        if (CXX_SUPPORTS_FBOUNDS_CHECK)
            target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-fbounds-check>)
        endif()
        check_cxx_compiler_flag(-fsanitize-trap=all CXX_SUPPORTS_FSANITIZE_TRAP_ALL)
        if (CXX_SUPPORTS_FSANITIZE_TRAP_ALL)
            target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-fsanitize-trap=all>)
        endif()
    endif()
    # enable some helpful GNU style compiler warnings (for CI to enforce code style)
    check_cxx_compiler_flag(-Wall CXX_SUPPORTS_WALL)
    if (CXX_SUPPORTS_WALL)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Wall>)
    endif()
    check_cxx_compiler_flag(-Wextra CXX_SUPPORTS_WEXTRA)
    if (CXX_SUPPORTS_WEXTRA)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Wextra>)
    endif()
    check_cxx_compiler_flag(-Wpedantic CXX_SUPPORTS_WPEDANTIC)
    if (CXX_SUPPORTS_WPEDANTIC)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Wpedantic>)
    endif()
    check_cxx_compiler_flag(-pedantic-errors CXX_SUPPORTS_PEDANTIC_ERRORS)
    if (CXX_SUPPORTS_PEDANTIC_ERRORS)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-pedantic-errors>)
    endif()
    check_cxx_compiler_flag(-Werror=return-type CXX_SUPPORTS_WRETURN_TYPE)
    if (CXX_SUPPORTS_WRETURN_TYPE)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Werror=return-type>)
    endif()
    check_cxx_compiler_flag(-Werror=missing-declarations CXX_SUPPORTS_WMISSING_DECLARATIONS)
    if (CXX_SUPPORTS_WMISSING_DECLARATIONS)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Werror=missing-declarations>)
    endif()
    check_cxx_compiler_flag(-Werror=uninitialized CXX_SUPPORTS_WUNINITIALIZED)
    if (CXX_SUPPORTS_WUNINITIALIZED)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Werror=uninitialized>)
    endif()
    check_cxx_compiler_flag(-Wmaybe-uninitialized CXX_SUPPORTS_WMAYBE_UNINITIALIZED)
    if (CXX_SUPPORTS_WMAYBE_UNINITIALIZED)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Wmaybe-uninitialized>)
    endif()
    # check_cxx_compiler_flag(-Wshadow CXX_SUPPORTS_WSHADOW)
    # if (CXX_SUPPORTS_WSHADOW)
    #     target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Wshadow>)
    # endif()
    # check_cxx_compiler_flag(-Wconversion CXX_SUPPORTS_WCONVERSION)
    # if (CXX_SUPPORTS_WCONVERSION)
    #     target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Wconversion>)
    # endif()
    check_cxx_compiler_flag(-Wnarrowing CXX_SUPPORTS_WNARROWING)
    if (CXX_SUPPORTS_WNARROWING)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Wnarrowing>)
    endif()
    check_cxx_compiler_flag(-Werror=delete-non-virtual-dtor CXX_SUPPORTS_WDELETE_NON_VIRTUAL_DTOR)
    if (CXX_SUPPORTS_WDELETE_NON_VIRTUAL_DTOR)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Werror=delete-non-virtual-dtor>)
    endif()
    check_cxx_compiler_flag(-Werror=suggest-override CXX_SUPPORTS_WSUGGEST_OVERRIDE)
    if (CXX_SUPPORTS_WSUGGEST_OVERRIDE)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Werror=suggest-override>)
    endif()
    check_cxx_compiler_flag(-Wunused CXX_SUPPORTS_WUNUSED)
    if (CXX_SUPPORTS_WUNUSED)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Wunused>)
    endif()
    check_cxx_compiler_flag(-Werror=return-local-addr CXX_SUPPORTS_WRETURN_LOCAL_ADDR)
    if (CXX_SUPPORTS_WRETURN_LOCAL_ADDR)
        target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Werror=return-local-addr>)
    endif()
    # check_cxx_compiler_flag(-Wzero-as-null-pointer-constant CXX_SUPPORTS_WZERO_AS_NULL_POINTER_CONSTANT)
    # if (CXX_SUPPORTS_WZERO_AS_NULL_POINTER_CONSTANT)
    #     target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Wzero-as-null-pointer-constant>)
    # endif()
    # check_cxx_compiler_flag(-Wnull-dereference CXX_SUPPORTS_WNULL_DEREFERENCE)
    # if (CXX_SUPPORTS_WNULL_DEREFERENCE)
    #     target_compile_options(${PROJECT_NAME} PRIVATE $<$<COMPILE_LANG_AND_ID:CXX,${CMAKE_CXX_COMPILER_ID}>:-Wnull-dereference>)
    # endif()
endif()

if (ZENO_ENABLE_AVX2)
    if (MSVC)
        check_cxx_compiler_flag(-arch:AVX2 CXX_SUPPORTS_MSVC_ARCH_AVX2)
        if (CXX_SUPPORTS_MSVC_ARCH_AVX2)
            target_compile_options(${PROJECT_NAME} PUBLIC $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:-arch:AVX2>)
            target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_AVX2)
        endif()
    endif()
    if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
        check_cxx_compiler_flag(-march=skylake CXX_SUPPORTS_MARCH_SKYLAKE)
        if (CXX_SUPPORTS_MARCH_SKYLAKE)
            target_compile_options(${PROJECT_NAME} PUBLIC $<$<COMPILE_LANG_AND_ID:CXX,GNU>:-march=skylake>)
            target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_AVX2)
        endif()
    endif()
endif()
if (ZENO_ENABLE_FAST_MATH)
    if (MSVC)
        check_cxx_compiler_flag(-arch:AVX2 CXX_SUPPORTS_MSVC_FP_FAST)
        if (CXX_SUPPORTS_MSVC_FP_FAST)
            target_compile_options(${PROJECT_NAME} PUBLIC $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:-fp:fast>)
            target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_FAST_MATH)
        endif()
    elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
        check_cxx_compiler_flag(-ffast-math CXX_SUPPORTS_FFAST_MATH)
        if (CXX_SUPPORTS_FFAST_MATH)
            target_compile_options(${PROJECT_NAME} PUBLIC $<$<COMPILE_LANG_AND_ID:CXX,GNU>:-ffast-math>)
            target_compile_definitions(${PROJECT_NAME} PUBLIC -DZENO_HAS_FAST_MATH)
        endif()
    endif()
endif()

if (ZENO_INSTALL_TARGETS)
    if (WIN32)
        install(DIRECTORY ci/win/dlls/ DESTINATION bin COMPONENT "windows_dlls")
        install(DIRECTORY ci/win/launcher/ DESTINATION . COMPONENT "windows_launcher")
    endif()
    if (UNIX AND NOT APPLE)
        install(DIRECTORY ci/share/applications/ DESTINATION share/applications COMPONENT "linux_desktop_entry")
    endif()
    install(DIRECTORY assets/ DESTINATION assets COMPONENT "assets")
    install(DIRECTORY docs/ DESTINATION docs COMPONENT "docs")
    install(
        EXPORT ZenoTargets
        FILE ZenoConfig.cmake
        NAMESPACE Zeno::
        DESTINATION lib/cmake/Zeno
        COMPONENT "develop"
        )
endif()

if (WIN32)
    target_sources(${PROJECT_NAME} PRIVATE ci/win/zeno.rc ci/win/zeno_rc.h)
    target_include_directories(${PROJECT_NAME} PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/ci/win>)
endif()

if (ZENO_ENABLE_SPLASH)
    add_subdirectory(ci/win/splash)
    add_dependencies(${PROJECT_NAME} splashw)
    target_compile_definitions(${PROJECT_NAME} PRIVATE -DZENO_HAS_SPLASH)
endif()

if (ZENO_ENABLE_DOCVIEW)
    add_subdirectory(3rd/md4c)
    add_dependencies(${PROJECT_NAME} md2html)
endif()

if (ZENO_RUN_CPACK)
    # we will use NSIS64 and 7Z for win, DEB and STGZ for linux
    if (WIN32)
        set(CPACK_GENERATOR "NSIS64;7Z")
    elseif (UNIX AND NOT APPLE)
        set(CPACK_GENERATOR "DEB;STGZ")
    endif()
    set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
    set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
    set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}")
    set(CPACK_PACKAGE_VENDOR "zenustech")
    set(CPACK_PACKAGE_DESCRIPTION_FILE ${PROJECT_SOURCE_DIR}/README.md)
    set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE)
    set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/ci/WELCOME.txt)
    set(CPACK_RESOURCE_FILE_WELCOME ${PROJECT_SOURCE_DIR}/ci/WELCOME.txt)
    set(CPACK_PACKAGE_EXECUTABLES ${PROJECT_NAME} ${PROJECT_NAME})
    if (WIN32)
        set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/assets\\\\logo.ico)
    else()
        set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/assets/logo.ico)
    endif()
    set(CPACK_PACKAGE_CONTACT "彭于斌 <pengyb@zenustech.com>")
    set(CPACK_STRIP_FILES TRUE)
    set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libstdc++6, libgcc-s1, libdw1, libelf1, libatomic1, libzstd1, libbz2-1.0, liblzma5, zlib1g, libgomp1, libglapi-mesa, libx11-6, libxext6, libxau6, libxdmcp6, libuuid1, libxcb1, libsm6, libice6")
    set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL TRUE)
    #set(CPACK_NSIS_MODIFY_PATH TRUE)
    set(CPACK_NSIS_MUI_FINISHPAGE_RUN ${PROJECT_NAME})
    set(CPACK_NSIS_INSTALL_ROOT "C:\\\\Program Files")
    set(CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\${PROJECT_NAME}.exe")
    set(CPACK_NSIS_URL_INFO_ABOUT "${PROJECT_HOMEPAGE_URL}")
    set(CPACK_NSIS_HELP_LINK "${PROJECT_HOMEPAGE_URL}")
    set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "CreateShortCut \\\"$DESKTOP\\\\${PROJECT_NAME}.lnk\\\" \\\"$INSTDIR\\\\bin\\\\${PROJECT_NAME}.exe\\\"")
    set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "Delete \\\"$DESKTOP\\\\${PROJECT_NAME}.lnk\\\"")
    get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS)
    list(REMOVE_ITEM CPACK_COMPONENTS_ALL "develop")
    set(CPACK_PROJECT_CONFIG_FILE ${PROJECT_SOURCE_DIR}/package.cmake)
    include(CPack)
endif()
